﻿namespace EyeOpen.VisualStudio2005BackMigration
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Xml.Linq;

    public class MigrationService
    {
        private int processedSolutions = 0;
        private int processedProjects = 0;
        private int processedSource = 0;
        private int totalProcessed = 0;
        private int partialsProcessed = 0;

        public event EventHandler<FileProcessedEventArgs> FileProcessed;

        public event EventHandler<EventArgs> Completed;

        /// <summary>
        /// Gets the processed solutions.
        /// </summary>
        /// <value>The processed solutions.</value>
        public int ProcessedSolutions
        {
            get { return this.processedSolutions; }
        }

        /// <summary>
        /// Gets the processed projects.
        /// </summary>
        /// <value>The processed projects.</value>
        public int ProcessedProjects
        {
            get { return this.processedProjects; }
        }

        /// <summary>
        /// Gets the processed source.
        /// </summary>
        /// <value>The processed source.</value>
        public int ProcessedSource
        {
            get { return this.processedSource; }
        }

        /// <summary>
        /// Gets the total processed.
        /// </summary>
        /// <value>The total processed.</value>
        public int TotalProcessed
        {
            get { return this.totalProcessed; }
        }

        /// <summary>
        /// Hierarchical Visual Studio 2008 solution migrates in all sub folders to Visual Studio 2005 solution.
        /// </summary>
        /// <param name="sourcePath">The source path.</param>
        /// <param name="destinationPath">The destination path.</param>
        public void MigrateBack(string sourcePath, string destinationPath)
        {
            if (sourcePath.Equals(destinationPath, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new ArgumentException("Source path and destination path cannot be equals.");
            }

            DirectoryInfo sourceDirectory = new DirectoryInfo(sourcePath);
            FileInfo[] sourceFiles = sourceDirectory.GetFiles("*.*", SearchOption.AllDirectories);

            this.processedSolutions = 0;
            this.processedProjects = 0;
            this.processedSource = 0;
            this.totalProcessed = 0;
            this.partialsProcessed = 0;

            this.MigrateAll(sourceFiles, new DirectoryInfo(sourcePath), new DirectoryInfo(destinationPath));
        }

        /// <summary>
        /// Raises the <see cref="E:FileProcessed"/> event.
        /// </summary>
        /// <param name="e">The <see cref="EyeOpen.VisualStudio2005BackMigration.FileProcessedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnFileProcessed(FileProcessedEventArgs e)
        {
            EventHandler<FileProcessedEventArgs> handler = this.FileProcessed;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:Completed"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnCompleted(EventArgs e)
        {
            EventHandler<EventArgs> handler = this.Completed;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Migrates all.
        /// </summary>
        /// <param name="sourceFiles">The source files.</param>
        /// <param name="sourcePath">The source path.</param>
        /// <param name="destinationPath">The destination path.</param>
        private void MigrateAll(FileInfo[] sourceFiles, DirectoryInfo sourcePath, DirectoryInfo destinationPath)
        {
            IEnumerable<FileInfo> filteredFiles =
                from sourceFile in sourceFiles
                where
                   !sourceFile.Directory.Name.Equals("bin", StringComparison.InvariantCultureIgnoreCase) &&
                   !sourceFile.Directory.Name.Equals("obj", StringComparison.InvariantCultureIgnoreCase) &&
                   !sourceFile.Directory.Name.Equals("debug", StringComparison.InvariantCultureIgnoreCase) &&
                   !sourceFile.Extension.Equals(".exe", StringComparison.InvariantCultureIgnoreCase) &&
                   !sourceFile.Extension.Equals(".exe", StringComparison.InvariantCultureIgnoreCase) &&
                   !sourceFile.Extension.Equals(".manifest", StringComparison.InvariantCultureIgnoreCase) &&
                   !sourceFile.Extension.Equals(".suo", StringComparison.InvariantCultureIgnoreCase) &&
                   !sourceFile.Extension.Equals(".gpstate", StringComparison.InvariantCultureIgnoreCase)
                select sourceFile;

            this.totalProcessed = filteredFiles.Count();

            foreach (FileInfo file in filteredFiles)
            {
                string destionationFilePath = file.FullName.Replace(sourcePath.FullName, destinationPath.FullName);

                FileInfo destinationFile = new FileInfo(destionationFilePath);

                if (file.FullName.Equals(destinationFile.FullName, StringComparison.InvariantCultureIgnoreCase))
                {
                    ////Prevent original file overwrite
                    throw new Exception();
                }

                if (!destinationFile.Directory.Exists)
                {
                    destinationFile.Directory.Create();
                }

                switch (file.Extension.ToLower())
                {
                    case ".sln":
                        File.WriteAllText(destinationFile.FullName, this.GetConvertedSolutionFile(file));
                        this.processedSolutions++;
                        break;
                    case ".csproj":
                        this.GetConvertedProjectFile(file).Save(destinationFile.FullName);
                        this.processedProjects++;
                        break;
                    case ".cs":
                        File.WriteAllText(destinationFile.FullName, this.GetConvertedSourceFile(file));
                        this.processedSource++;
                        break;
                    default:
                        file.CopyTo(destinationFile.FullName, true);
                        break;
                }

                this.partialsProcessed++;

                this.OnFileProcessed(new FileProcessedEventArgs(file, destinationFile, this.partialsProcessed, this.totalProcessed));
            }

            this.OnCompleted(EventArgs.Empty);
        }

        /// <summary>
        /// Gets the converted source file.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <returns></returns>
        private string GetConvertedSourceFile(FileInfo file)
        {
            StringBuilder sourceContent = new StringBuilder(File.ReadAllText(file.FullName));
            sourceContent.Replace("using System.Linq;", string.Empty);
            sourceContent.Replace("using System.Xml.Linq;", string.Empty);
            return sourceContent.ToString();
        }

        /// <summary>
        /// Gets the converted project file.
        /// </summary>
        /// <param name="projectFile">The project file.</param>
        /// <returns></returns>
        private XElement GetConvertedProjectFile(FileInfo projectFile)
        {
            XElement projectContentElement = XElement.Load(projectFile.FullName);
            projectContentElement.Attribute("ToolsVersion").Remove();

            IEnumerable<XElement> nodes = from targetFrameworkNode in projectContentElement.Elements()
                                          select targetFrameworkNode;

            XElement targetFrameworkVersion = (from childNode in nodes.Elements()
                                               where childNode.Name.LocalName == "TargetFrameworkVersion"
                                               select childNode).FirstOrDefault();
            if (targetFrameworkVersion != null)
            {
                targetFrameworkVersion.Remove();
            }

            XElement productVersion = (from childNode in nodes.Elements()
                                       where childNode.Name.LocalName == "ProductVersion"
                                       select childNode).First();
            productVersion.SetValue("8.0.50727");

            (from childNode in nodes.Elements()
             where childNode.Name.LocalName == "Reference" && childNode.Value == "3.5"
             select childNode).Remove();

            XElement import = (from childNode in projectContentElement.Elements()
                               where childNode.Name.LocalName == "Import" && childNode.Attribute("Project").Value == @"$(MSBuildToolsPath)\Microsoft.CSharp.targets"
                               select childNode).FirstOrDefault();

            if (import != null)
            {
                import.Attribute("Project").Value = import.Attribute("Project").Value.Replace("MSBuildToolsPath", "MSBuildBinPath");
            }

            return projectContentElement;
        }

        /// <summary>
        /// Gets the converted solution file.
        /// </summary>
        /// <param name="solutionFile">The solution file.</param>
        /// <returns></returns>
        private string GetConvertedSolutionFile(FileInfo solutionFile)
        {
            string solutionContent = File.ReadAllText(solutionFile.FullName);
            solutionContent = solutionContent.Replace("Microsoft Visual Studio Solution File, Format Version 10.00\r\n# Visual Studio 2008", "Microsoft Visual Studio Solution File, Format Version 9.00\r\n# Visual Studio 2005");
            return solutionContent;
        }
    }
}